iT邦幫忙

2023 iThome 鐵人賽

DAY 22
0
Software Development

Laravel 後端菜鳥可以知道的流程概念系列 第 22

匯出加密的 PDF(1):wkhtmltopdf、laravel-snappy

  • 分享至 

  • xImage
  •  

鐵人賽備註

雙十連假,讓我先用庫存擋擋呀~~~~

這篇主要是紀錄兩個套件的使用,以及碰到的問題,希望對那些被業主有同樣要求的夥伴有個參考XD


開發上常常碰到業主有匯出 PDF 檔案的需求,剛好做到這個功能所以記錄下來

  • 業主說之前有用 wkhtmltopdf 套件,所以優先嘗試這個套件
  • 查了一下,wkhtmltopdf 是直接在 terminal 使用的套件,可以直接把網頁變成 PDF 檔案
  • Laravel 裏面,有開發者另外包成 Snappy 套件,方便在 Laravel 使用
  • 另外 wkhtmltopdf 沒有支援輸出 pdf 檔案加密,所以另外需要 pdftk 套件
  • 所以安裝過程會是:wkhtmltopdf、snappy、pdftk ( pdftk 放在第二篇 )
  • 其他常聽到的套件包括 dompPdf 似乎就可以直接處理 PDF + 設定密碼 ,有興趣可以再嘗試一下

https://ithelp.ithome.com.tw/upload/images/20231007/20162893uZW1tpXq00.png

wkhtmltopdf 安裝

官方文件:GitHub官方網站使用文件

官方文件沒有,但因為我是使用 Mac os,所以我是用 homebrew 安裝:

```php
brew install --cask wkhtmltopdf
```

安裝完後輸入 wkhtmltopdf 就會有相關資訊

https://ithelp.ithome.com.tw/upload/images/20231007/201628932KYwpYsM3p.png

snappy github 有提到一些 wkhtmltopdf 已知檔案位置的問題,可以檢查一下有沒有安裝到確切位置

Attention vagrant users!

Move the binaries to a path that is not in a synced folder, for example:

cp vendor/h4cc/wkhtmltoimage-amd64/bin/wkhtmltoimage-amd64 /usr/local/bin/
cp vendor/h4cc/wkhtmltopdf-amd64/bin/wkhtmltopdf-amd64 /usr/local/bin/

and make it executable:

chmod +x /usr/local/bin/wkhtmltoimage-amd64
chmod +x /usr/local/bin/wkhtmltopdf-amd64

This will prevent the error 126.

Snappy

snappy 的 github文件算清楚,建議可以先自己閱讀試試看

安裝

在專案根目錄 terminal視窗輸入

composer require barryvdh/laravel-snappy

config/app.php 裡面,找到圖片 ‘providers’ 這個區塊,加入 serviceProvider.php

Barryvdh\Snappy\ServiceProvider::class,

https://ithelp.ithome.com.tw/upload/images/20231007/20162893NQUczoLSwo.png

然後建立 snappy 的 config 檔案

php artisan vendor:publish --provider="Barryvdh\Snappy\ServiceProvider"

我的 config 檔案裡面有加入一些頁面設定,最後長這樣

'pdf' => [
        'enabled' => true,
				// 這裡務必要確認 wkhtmltopdf 的安裝位置,要放在這邊
        'binary'  => env('WKHTML_PDF_BINARY', '/usr/local/bin/wkhtmltopdf'),
        'timeout' => false,
        'options' => [
            'page-size'        => 'A4',
            'javascript-delay' => 3000,
            'encoding'         => 'UTF-8', //顯示中文字建議放
            'zoom'             => 1,
        ],
        'env'     => [],
    ],

    'image' => [
        'enabled' => true,
        'binary'  => env('WKHTML_IMG_BINARY', '/usr/local/bin/wkhtmltoimage'),
        'timeout' => false,
        'options' => [],
        'env'     => [],
    ],

使用

使用上不難, github 給了很詳細的示範程式碼:https://github.com/barryvdh/laravel-snappy#usage

  1. inline() 是在瀏覽器視窗顯示、save()是存在專案裡面、download() 是直接下載。

  2. setOption() 函式要參考 wkhtmltopdf 的官方文件,可以寫在 config/snappy.php 或直接在 controller 程式碼當中

  3. 這裡用了表頭 header
    有些文件會固定在頁面右上角顯示 ID 號碼等等,且每一頁都要顯示的這種功能,以 word 編輯來說會放在 頁首/頁尾,wkhtmltopdf 官方文件有寫到,這種功能要有 頁首html 及內容html 兩個html檔案去渲染

    $group = Group::find($groupId);
    
        // 內容 html
    $pdf = SnappyPdf::loadView('groupPdf', [
       'group' => $group,
    ]);
    
        // 頁首 html
    $header = view('groupHeaderPdf', [  
        'group' => $group,
    ]);
    
        // 用 setOption 加入頁首
    $pdf->setOption('header-html', $header)
        ->save(base_path("groups/$fileName.pdf"), true);
    

    wkhtmltopdf 官方文件有 header & footer 說明及一些可用變數

    https://ithelp.ithome.com.tw/upload/images/20231007/20162893LxawOPNzxc.png

到此應該可以產出PDF檔案了吧~可以先用 download() 下載看看產生的 PDF 是否符合需求。

提供常碰到的問題與方向:(最困難的在這….建議可以開個AWS EC2或其他測試環境,避免把自己電腦搞壞)

  1. 跑版 → 重新調整 html 版型,可能會遇到套件或是部分 CDN 引用不支援的問題
  2. 中文未顯示 → html 沒有 UTF-8 標籤、電腦沒有安裝特定字型等
  3. html 連結的檔案位置抓不到 → 我在這裡卡了一點時間,可以善用 laravel base_path()、storage_path()、public_path() 等 helper function 處理

上面錯誤可能不會有明確錯誤訊息,所以處理起來超麻煩….

碰到的問題

1. 不明錯誤訊息、html 連結失敗

前端提供的 html 畫面節錄

<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <link rel="stylesheet" type="text/css" **href="storage/assets/css/common.css"/> 
        //問題出在這裡**
</head>

用 postman 打 api 出現的錯誤訊息
The exit status code '1' says something went wrong:

https://ithelp.ithome.com.tw/upload/images/20231007/20162893ocntWjZqKa.png

為了這個錯誤訊息真是搞了一整天,向 mentor 求救:

  1. 稍微整理一下錯誤訊息,看有沒有一些關鍵字
  2. 再從關鍵字找線索
"message": "The exit status code '1' says something went wrong:\n
stderr: \"Loading pages (1/6)\n
[>                                                           ] 0%\r
[======>                                                     ] 10%\r
Warning: Blocked access to file                                   \n
Warning: Blocked access to file \n
Warning: Blocked access to file \n
Warning: Blocked access to file \n[
==========>                                                 ] 17%\r
Error: Failed to load about:blank, with network status code 301 and http status code 0 - Protocol \"about\" is unknown\n
Error: Failed to load about:blank, with network status code 301 and http status code 0 - Protocol \"about\" is unknown\n
Error: Failed to load about:blank, with network status code 301 and http status code 0 - Protocol \"about\" is unknown\n
Error: Failed to load about:blank, with network status code 301 and http status code 0 - Protocol \"about\" is unknown\n
[=============>                                              ] 23%\r
[================>                                           ] 27%\r
[===================>                                        ] 32%\r
[=======================>                                    ] 39%\r
Warning: Blocked access to file                                   \n
Warning: Blocked access to file \nError: Failed to load about:blank, with network status code 301 and http status code 0 - Protocol \"about\" is unknown\n
Error: Failed to load about:blank, with network status code 301 and http status code 0 - Protocol \"about\" is unknown\n
[============================================================] 100%\rCounting pages (2/6)                                               \n
[============================================================] Object 1 of 1\r
Resolving links (4/6)                                                       \n
[============================================================] Object 1 of 1\r
Loading headers and footers (5/6)                                           \n
Printing pages (6/6)\n
[>                                                           ] Preparing\r
[===============>                                            ] Page 1 of 4\r
[==============================>                             ] Page 2 of 4\r
[=============================================>              ] Page 3 of 4\r
[============================================================] Page 4 of 4\r
Done                                                                      \n
Exit with code 1 due to network error: ProtocolUnknownError\n
\"\nstdout: \"\"\n
command: /usr/local/bin/wkhtmltopdf --lowquality '/var/tmp/knp_snappy64f7428490b5b2.29394085.html' '/var/tmp/knp_snappy64f7428490f604.17908708.pdf'.",
"exception": "RuntimeException",
"file": "/Users/mia/Sites/luxgen/luxgen_backed/vendor/knplabs/knp-snappy/src/Knp/Snappy/AbstractGenerator.php",
"line": 469,

從錯誤訊息中可以看到似乎是什麼檔案讀不到,又前端給的 HTML 檔案有幾個 CDN 引用的檔案,稍微測試一下後發現,如果 HTML 只留下純文字,是可以正常顯示的。

因此修改了前端給的 HTML 檔案,把圖片連結檔案跟前端引入的 CDN 都拿掉,文字就出現了!!

但版型還是需要的,後來去確認外部連結的檔案位置、用 helper function 設定 assets() 才成功

<link rel="stylesheet" type="text/css" href="{{ asset('storage/assets/css/common.css') }}"/>

小撇步:可以用 tinker 測試路徑正不正確

2. 請求超過時間

當我改完 assets() 後,每次送出請求仍然等很久,最後顯示請求處理時間過長。

後來找到這篇:https://github.com/barryvdh/laravel-snappy/issues/237

意思是我的專案伺服器用 php artisan serve 啟動,php artisan serve 是 Laravel 框架提供的用於在本地運行開發伺服器的命令,方便開發和測試 Laravel 應用程序。這個方式非常簡便,但無法像 Nginx 或 Apache 支援更多複雜的功能,例如在處理這個請求時, php artisan serve 沒辦法同時要外連到指定的檔案 (上面的 assets() 檔案),導致請求超過處理時間。

結論還是要去 AWS 開一個測試機進行測試…然後衍伸出了,因為檔案權限不符合nginx設置,所以報錯的問題。預計要儲存的位置,檔案權限要是www-data

https://ithelp.ithome.com.tw/upload/images/20231007/20162893wrMkJZF2DN.png

drwxrwxr-x  2 www-data www-data  4096 Sep 18 09:56 .    
    // 這個存放的資料夾要能讓 nginx 操作
drwxrwxr-x 15 ubuntu   ubuntu    4096 Sep 18 09:47 ..
-rw-rw-r--  1 ubuntu   ubuntu   39435 Sep 18 09:47 A0223000003.pdf
-rw-r--r--  1 www-data www-data 39879 Sep 18 09:56 A0223000006.pdf

3. The output file’s dictionary could not be creates

這個問題的起因是因為我沒有把 filesystem 的寫法搞懂,導致程式碼無法在指定的資料夾儲存生成的 PDF。有興趣的話可以先看一下官方文件 filesystem 章節,明天會詳細看一下我的檔案到底怎麼拿、從何處下載。

到這邊應該可以正常產出 PDF 檔案了吧XD

密碼設定要用到下一個套件: pdftk,就留在下一篇囉!


上一篇
Laravel Mutator & Accessor / 修改取得圖片路徑的方式
下一篇
匯出加密的 PDF(2):pdftk
系列文
Laravel 後端菜鳥可以知道的流程概念30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言